package com.ehotting.eaf.core.utils;

import com.alibaba.fastjson.JSON;
import com.ehotting.eaf.core.domain.FileListItem;
import com.ehotting.eaf.core.domain.FileListReqBody;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.springframework.util.Base64Utils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

@Slf4j
public class OkHttpUtils {
    private static volatile OkHttpClient okHttpClient = null;
    private static volatile Semaphore semaphore = null;
    private Map<String, String> headerMap;
    private Map<String, String> paramMap;
    private Object bodyInfo;
    private String url;
    private Request.Builder request;

    /**
     * 初始化okHttpClient，并且允许https访问
     */
    private OkHttpUtils() {
        if (okHttpClient == null) {
            synchronized (OkHttpUtils.class) {
                if (okHttpClient == null) {
                    TrustManager[] trustManagers = buildTrustManagers();
                    okHttpClient = new OkHttpClient.Builder()
                            .connectTimeout(10, TimeUnit.MINUTES)
                            .writeTimeout(10, TimeUnit.MINUTES)
                            .readTimeout(10, TimeUnit.MINUTES)
                            .sslSocketFactory(createSSLSocketFactory(trustManagers), (X509TrustManager) trustManagers[0])
                            .hostnameVerifier((hostName, session) -> true)
                            .retryOnConnectionFailure(true)
                            .build();
                    addHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36");
                }
            }
        }
    }

    /**
     *
     * @param connectTimeout unit SECONDS
     * @param writeTimeout unit SECONDS
     * @param readTimeout unit SECONDS
     */
    private OkHttpUtils(long connectTimeout,long writeTimeout,long readTimeout) {
        if (okHttpClient == null) {
            synchronized (OkHttpUtils.class) {
                if (okHttpClient == null) {
                    TrustManager[] trustManagers = buildTrustManagers();
                    okHttpClient = new OkHttpClient.Builder()
                            .connectTimeout(connectTimeout, TimeUnit.SECONDS)
                            .writeTimeout(writeTimeout, TimeUnit.SECONDS)
                            .readTimeout(readTimeout, TimeUnit.SECONDS)
                            .sslSocketFactory(createSSLSocketFactory(trustManagers), (X509TrustManager) trustManagers[0])
                            .hostnameVerifier((hostName, session) -> true)
                            .retryOnConnectionFailure(true)
                            .build();
                    addHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36");
                }
            }
        }
    }

    /**
     * 用于异步请求时，控制访问线程数，返回结果
     *
     * @return
     */
    private static Semaphore getSemaphoreInstance() {
        //只能1个线程同时访问
        synchronized (OkHttpUtils.class) {
            if (semaphore == null) {
                semaphore = new Semaphore(0);
            }
        }
        return semaphore;
    }

    /**
     * 创建OkHttpUtils
     *
     * @return
     */
    public static OkHttpUtils builder() {
        return new OkHttpUtils();
    }

    public static OkHttpUtils builder(long connectTimeout,long writeTimeout,long readTimeout) {
        return new OkHttpUtils( connectTimeout, writeTimeout, readTimeout);
    }

    /**
     * 添加url
     *
     * @param url
     * @return
     */
    public OkHttpUtils url(String url) {
        this.url = url;
        return this;
    }

    /**
     * 添加参数
     *
     * @param key   参数名
     * @param value 参数值
     * @return
     */
    public OkHttpUtils addParam(String key, String value) {
        if (paramMap == null) {
            paramMap = new LinkedHashMap<>(16);
        }
        paramMap.put(key, value);
        return this;
    }

    /**
     * 添加参数
     *
     * @return
     */
    public OkHttpUtils addParam(Map<String, String> map) {
        if (CollectionUtils.isEmpty(map)) {
            return this;
        }
        if (paramMap == null) {
            paramMap = new LinkedHashMap<>(16);
        }
        paramMap.putAll(map);
        return this;
    }

    /**
     * 添加requestBody信息
     */
    public OkHttpUtils addBody(Object bodyInfo) {
        this.bodyInfo = bodyInfo;
        return this;
    }

    /**
     * 添加请求头
     *
     * @param key   参数名
     * @param value 参数值
     * @return
     */
    public OkHttpUtils addHeader(String key, String value) {
        if (headerMap == null) {
            headerMap = new LinkedHashMap<>(16);
        }
        headerMap.put(key, value);
        return this;
    }

    /**
     * 添加请求头
     *
     * @return
     */
    public OkHttpUtils addHeader(Map<String, String> map) {
        if (CollectionUtils.isEmpty(map)) {
            return this;
        }
        if (headerMap == null) {
            headerMap = new LinkedHashMap<>(16);
        }
        headerMap.putAll(map);
        return this;
    }

    /**
     * 初始化get方法
     *
     * @return
     */
    public OkHttpUtils get() {
        request = new Request.Builder().get();
        request.url(packUrlParam());
        return this;
    }

    private String packUrlParam() {
        return packUrlParam(this.paramMap);
    }

    private String packUrlParam(Map<String, String> paramMap){
        StringBuilder urlBuilder = new StringBuilder(url);
        if (paramMap != null) {
            urlBuilder.append("?");
            try {
                for (Map.Entry<String, String> entry : paramMap.entrySet()) {
                    urlBuilder.append(URLEncoder.encode(entry.getKey(), "utf-8")).
                            append("=").
                            append(URLEncoder.encode(entry.getValue(), "utf-8")).
                            append("&");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            urlBuilder.deleteCharAt(urlBuilder.length() - 1);
        }
        return urlBuilder.toString();
    }

    /**
     * 初始化post方法
     *
     * @param isJsonPost true等于json的方式提交数据，类似postman里post方法的raw
     *                   false等于普通的表单提交
     * @return
     */
    public OkHttpUtils post(boolean isJsonPost) {
        RequestBody requestBody;
        if (isJsonPost) {
            String json = "";
            if (paramMap != null) {
                json = JSON.toJSONString(paramMap);
            }
            requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json);
        } else {
            FormBody.Builder formBody = new FormBody.Builder();
            if (paramMap != null) {
                paramMap.forEach(formBody::add);
            }
            requestBody = formBody.build();
        }
        request = new Request.Builder().post(requestBody).url(url);
        return this;
    }

    public OkHttpUtils postBody(boolean isJsonPost) {
        RequestBody requestBody = getBody(isJsonPost);
        request = new Request.Builder().post(requestBody).url(url);
        return this;
    }

    public OkHttpUtils delete(boolean isJsonPost) {
        RequestBody requestBody = getBody(isJsonPost);
        request = new Request.Builder().delete(requestBody).url(url);
        return this;
    }

    private RequestBody getBody(boolean isJsonPost) {
        RequestBody requestBody;
        if (isJsonPost) {
            String json = "";
            if (bodyInfo != null) {
                json = JSON.toJSONString(bodyInfo);
            }
            requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json);
        } else {
            FormBody.Builder formBody = new FormBody.Builder();
            if (paramMap != null) {
                paramMap.forEach(formBody::add);
            }
            requestBody = formBody.build();
        }
        return requestBody;
    }

    /**
     * 同步请求
     *
     * @return
     */
    public String sync() {
        setHeader(request);
        ByteArrayOutputStream os = null;
        BufferedOutputStream outputStream = null;
        try {
            Response response = okHttpClient.newCall(request.build()).execute();
            log.info("response.headers():{}",JSON.toJSONString(response.headers().toMultimap()));
            String contentType = response.header("Content-Type");
            log.info("contentType:{}", contentType);
            String fileContentType1 = "application/octet-stream";
            // fileContentType2是算法模块存在这种类型，所以加入判断，但是传给C端还是使用fileContentType1
            String fileContentType2 = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
            if (contentType!=null && (contentType.contains(fileContentType1)
                    || contentType.contains(fileContentType2))) {
                // 文件下载
                InputStream inputStream = response.body().byteStream();
                log.info("inputStream.available():{}",inputStream.available());
                os = new ByteArrayOutputStream();
                outputStream = new BufferedOutputStream(os);

                byte[] buffer = new byte[2048];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
                outputStream.flush();

//                sink = Okio.sink(os);
//                bufferedSink = Okio.buffer(sink);
//                log.info("response.body().source() :{}",response.body().source());
//                bufferedSink.writeAll(response.body().source());
                byte[] bytes = os.toByteArray();

                String string = Base64Utils.encodeToString(bytes);

                Map<String,Object> map = new HashMap<>();
                map.put("success",true);
                Map<String,String> headerMap = new HashMap<>();
                String fileMd5 = response.header("File-Md5");
                String myFileMd5 = DigestUtils.md5DigestAsHex(bytes).toUpperCase();
                log.info("response header get fileMd5 is :{}",fileMd5);
                log.info("myFileMd5:{}",myFileMd5);

                Map<String, List<String>> stringListMap = response.headers().toMultimap();
                for (String s : stringListMap.keySet()) {
                    headerMap.put(s.toLowerCase(), String.join(",", stringListMap.get(s)));
                }

                map.put("headers", headerMap);
                map.put("content-type", fileContentType1);
                String dispositionHeader = response.header("Content-Disposition");
                log.info("dispositionHeader:{}",dispositionHeader);
                if (!StringUtils.isEmpty(dispositionHeader)) {
                    String uselessStr = "filename=";
                    String filename = dispositionHeader.trim().substring(dispositionHeader.lastIndexOf(uselessStr)+uselessStr.length());
                    map.put("filename", filename);
                }
                map.put("data",string);

                return JSON.toJSONString(map);
            }
            assert response.body() != null;
            return response.body().string();
        } catch (IOException e) {
            e.printStackTrace();
            return "请求失败：" + e.getMessage();
        }finally {

            try {
                if (os!=null) {
                    os.close();
                }
                if (outputStream!=null) {
                    outputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 异步请求，有返回值
     */
    public String async() {
        StringBuilder buffer = new StringBuilder("");
        setHeader(request);
        okHttpClient.newCall(request.build()).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                buffer.append("请求出错：").append(e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                assert response.body() != null;
                buffer.append(response.body().string());
                getSemaphoreInstance().release();
            }
        });
        try {
            getSemaphoreInstance().acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return buffer.toString();
    }

    /**
     * 异步请求，带有接口回调
     *
     * @param callBack
     */
    public void async(ICallBack callBack) {
        setHeader(request);
        okHttpClient.newCall(request.build()).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                callBack.onFailure(call, e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                assert response.body() != null;
                callBack.onSuccessful(call, response.body().string());
            }
        });
    }

    /**
     * 为request添加请求头
     *
     * @param request
     */
    private void setHeader(Request.Builder request) {
        if (headerMap != null) {
            try {
                for (Map.Entry<String, String> entry : headerMap.entrySet()) {
                    if (entry.getValue() == null) {
                        continue;
                    }
                    request.addHeader(entry.getKey(), entry.getValue());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 生成安全套接字工厂，用于https请求的证书跳过
     *
     * @return
     */
    private static SSLSocketFactory createSSLSocketFactory(TrustManager[] trustAllCerts) {
        SSLSocketFactory ssfFactory = null;
        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new SecureRandom());
            ssfFactory = sc.getSocketFactory();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ssfFactory;
    }

    private static TrustManager[] buildTrustManagers() {
        return new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[]{};
                    }
                }
        };
    }

    /**
     * 自定义一个接口回调
     */
    public interface ICallBack {

        void onSuccessful(Call call, String data);

        void onFailure(Call call, String errorMsg);

    }

    public String uploadFile() {
        try {
            String fileName = paramMap.get("fileName");
            String fileContent = paramMap.get("fileContent");

            MultipartBody body = new MultipartBody.Builder().setType(MultipartBody.FORM)
                    .addFormDataPart("uploadFile", fileName,
                            RequestBody.create(MediaType.parse("application/octet-stream"),
                                    fileContent.getBytes())).build();
            request = new Request.Builder().post(body).url(url);
            setHeader(request);
            Response response = okHttpClient.newCall(request.build()).execute();
            assert response.body() != null;
            return response.body().string();
        } catch (IOException e) {
            e.printStackTrace();
            return "请求失败：" + e.getMessage();
        }
    }

    /**
     * 算法设计的上传
     * @return
     */
    public String uploadFileByDp() {
        try {
            String fileContent;
            MultipartBody body;
            String fileName = paramMap.get("fileName");

            MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
            if (paramMap.containsKey("File")){
                fileContent = paramMap.get("File");
                String base64Content = fileContent.split(",")[1];
                builder.addFormDataPart("File", fileName==null?"fileName":fileName,
                                RequestBody.create(MediaType.parse("application/octet-stream"),
                                        Base64Utils.decodeFromString(base64Content)));
            }
            if (paramMap.containsKey("file")){
                fileContent = paramMap.get("file");
                String base64Content = fileContent.split(",")[1];
                builder.addFormDataPart("file", fileName==null?"fileName":fileName,
                                RequestBody.create(MediaType.parse("application/octet-stream"),
                                        Base64Utils.decodeFromString(base64Content)));
            }

            for (String key : paramMap.keySet()) {
                if (key.equalsIgnoreCase("file")){
                    continue;
                }
                builder.addFormDataPart(key,paramMap.get(key));
            }

            body = builder.build();
            log.info("url:{}",url);
            request = new Request.Builder().post(body).url(url);
            setHeader(request);
            Response response = okHttpClient.newCall(request.build()).execute();
            assert response.body() != null;

            return response.body().string();
        } catch (IOException e) {
            e.printStackTrace();
            return "请求失败：" + e.getMessage();
        }
    }

    /**
     * 黄埔材料院cae的上传（暂定）
     * 1、从paramMap中获取文件的base64字符串
     * 2、根据目标后端的情况，根据字符串获取byte数组
     * 3、将获取到的byte数组组装成form-data提交到目标后端
     * @return response的json格式字符串
     */
    public String uploadFileByCae() {
        try {
            String fileName = paramMap.get("fileName");
            String fileContent = paramMap.get("fileContent");

            MultipartBody body = new MultipartBody.Builder().setType(MultipartBody.FORM)
                    .addFormDataPart("uploadFile", fileName,
                            RequestBody.create(MediaType.parse("application/octet-stream"),
                                    fileContent.getBytes())).build();
            request = new Request.Builder().post(body).url(url);
            setHeader(request);
            Response response = okHttpClient.newCall(request.build()).execute();
            assert response.body() != null;
            return response.body().string();
        } catch (IOException e) {
            e.printStackTrace();
            return "请求失败：" + e.getMessage();
        }
    }

    public String uploadDir(String string) {
        try {
            FileListReqBody fileListReqBody = JSON.parseObject(string, FileListReqBody.class);
            List<FileListItem> fileList = fileListReqBody.getFileList();
            for (FileListItem fileListItem : fileList) {
                String fileContentBase64 = fileListItem.getFileContent();
                String fileContent = new String( Base64.getDecoder().decode(fileContentBase64));
                fileListItem.setFileContent(fileContent);
            }

            MultipartBody.Builder builder = new MultipartBody.Builder();
            builder.setType(MultipartBody.FORM);

            for (FileListItem fileListItem : fileList) {
                builder.addFormDataPart("fileName", fileListItem.getFileName(),
                                RequestBody.create(MediaType.parse("application/octet-stream"), fileListItem.getFileContent().getBytes()));
            }

            MultipartBody body = builder.build();
            request = new Request.Builder().post(body).url(url);
            setHeader(request);
            Response response = okHttpClient.newCall(request.build()).execute();
            assert response.body() != null;
            return response.body().string();
        } catch (IOException e) {
            e.printStackTrace();
            return "请求失败：" + e.getMessage();
        }
    }
}